﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Medianamik.Core.Sugar;
using Medianamik.Globalization;

namespace Medianamik.Core
{
    /// <summary>
    /// Classe représantant une Property d'un Type Medianamik
    /// </summary>
    [DebuggerDisplay("{Name} : {Value}" )]
    public class NodeProperty : IProperty
    {
        private readonly Guid _nodeId;

        /// <summary>
        /// Ce constructeur est requis pour utiliser NodeProperty avec des méthodes génériques
        /// </summary>
        internal NodeProperty()
        {

        }

        internal NodeProperty(int? pID, NodeTypeProperty pNodeTypeProperty, object pValue, string pCultureName, int? pRevision, Node pNode)
        {
            if (pCultureName == null)
            {
                throw new ArgumentNullException("pCultureName");
            }

            ID = pID;
            NodeTypeProperty = pNodeTypeProperty;
            Node = pNode;
            _value = InitialValue = pValue;
            Culture = CultureInfo.GetCultureInfo(pCultureName);
            Revision = pRevision;
            _nodeId = pNode.ID;

            if (pID == null)
                IsNew = true;

        }

        internal NodeProperty(NodeTypeProperty pNodeTypeProperty, object value, string cultureName, Node node)
            : this(default(int?), pNodeTypeProperty, value, cultureName, null /*Revision*/, node )
        {
            IsNew = true;
        }

        public int? ID
        {
            get;
            internal set;
        }

        private CultureInfo _culture;
        public CultureInfo Culture
        {
            get
            {
                if (IsLanguageNeutral)
                {
                    return CultureInfo.InvariantCulture;
                }
                return _culture;
            }
            internal set
            {
                _culture = value;
            }
        }

        public Guid NodeTypePropertyID
        {
            get
            {
                return NodeTypeProperty.ID;
            }
        }

        [NonSerialized]
        private int? _revision;
        public int? Revision
        {
            get { return _revision; }
            internal set { _revision = value; }
        }

        [NonSerialized]
        private DateTime? _newRevision;
        public DateTime? NewRevision
        {
            get { return _newRevision; }
            internal set { _newRevision = value; }
        }

        public bool IsNew
        {
            get;
            internal set;
        }

        internal virtual object InitialValue { get; set; }
        private object _value;
        public virtual object Value
        {
            get
            {
                return _value;
            }
            internal set
            {
                if(value == null)
                    _value = value;
                else
                {
                    var type = NodeTypeProperty.GetType(true);

                    _value = type == typeof(string) ? value : (value.ToString().Length == 0 ? null : value);
                }

                OnValueSet(EventArgs.Empty);
            }
        }

        /// <summary>
        /// IsDirty détermine si la valeur de la propriété a changé
        /// </summary>
        public virtual bool IsDirty
        {
            get
            {
                if (InitialValue != null)
                {
                    return !InitialValue.Equals(Value);
                }
                if (Value != null)
                {
                    return !Value.Equals(InitialValue);
                }

                return false;
            }
        }

        private bool? _isUrl;
        public bool IsUrl
        {
            get
            {
                if (!_isUrl.HasValue)
                    _isUrl = NodeTypePropertyID.Equals(MedianamikProperties.Url);

                return _isUrl.GetValueOrDefault();
           }
        }
        
        [NonSerialized]
        private Node _node;
        public Node Node
        {
            get { return _node; }
            internal set { _node = value; }
        }

        public NodeTypeProperty NodeTypeProperty
        {
            get;
            private set; 
        }

        public string Name
        {
            get
            {
                return NodeTypeProperty.Name;
            }
        }

        public bool HaveTooltip
        {
            get { return NodeTypeProperty.HaveTooltip; }
        }

        public bool IsLanguageNeutral
        {
            get
            {
                return NodeTypeProperty.IsLanguageNeutral;
            }
        }

        public bool IsRequired
        {
            get
            {
                return NodeTypeProperty.IsRequired;
            }
        }

        public virtual MedianamikControl MedianamikControl
        {
            get
            {
                return NodeTypeProperty.MedianamikControl;
            }
        }

        public string GroupName
        {
            get
            {
                return NodeTypeProperty.GroupName;
            }
        }

        public Guid? PrimaryTypeID
        {
            get { return NodeTypeProperty.PrimaryTypeID; }
        }

        public virtual bool IsLookup
        {
            get
            {
                return false;
            }
        }

        public DataType DataType
        {
            get { return NodeTypeProperty.DataType; }
        }

        public short? MaxLength
        {
            get { return NodeTypeProperty.MaxLength; }
        }

        public virtual bool IsOwnProperty
        {
            get
            {
                return true;
            }
        }

        public MedianamikControlInfo MedianamikControlInfo
        {
            get
            {

                return NodeTypeProperty.MedianamikControlInfo;
            }
        }

        public bool IsUnique
        {
            get
            {
                return NodeTypeProperty.IsUnique;
            }
        }

        public System.Text.RegularExpressions.Regex ValidationRegex
        {
            get
            {
                return NodeTypeProperty.ValidationRegex;
            }
        }

        public BitVector32 Flags
        {
            get
            {
                return NodeTypeProperty.Flags;
            }
        }

        public bool Indexable
        {
            get
            {
                return (IsOwnProperty && !IsLookup && NodeTypeProperty.Indexable);
            }
        }

        [NonSerialized]
        private int? _position;
        public int Position
        {
            get
            {
                return _position
                    ?? (_position = Node.NodeType.GetPropertyPosition(NodeTypePropertyID)).Value;
            }
        }

        public virtual  object GetValue()
        {
            return GetValue(IsLookup ? typeof (Guid?) : NodeTypeProperty.GetType(true));
        }
 
        public virtual T GetValue<T>()
        {
            if (Value == null) { return default(T); }
            if (Value is T)
            {
                //Aucune conversion nécessaire
                return (T)Value;
            }

            return (T)GetValue(typeof(T));
        }

        public virtual object GetValue(Type pType)
        {
            if (Value == null) { return null; }
            var tc = TypeDescriptor.GetConverter(pType);

            object result = null;

            try
            {
                result = tc.ConvertFromInvariantString(Value.ToString());
            }
            catch 
            {

            }

            return result;
        }

        public string GetStringValue(CultureInfo culture, string dateTimeFormat,
            string numberFormat)
        {
            if (Value == null || Value.ToString() == String.Empty) { return String.Empty; }

            if (NodeTypeProperty.IsLookup)
            {
                if (NodeTypeProperty.NodeType.IsManyToMany)
                {
                    var lookupNodes = GetValue<IEnumerable<Guid>>()
                        .Where(x => !x.Equals(Guid.Empty))
                        .Select(x => NodeManager.GetNode(x)).NotNull();

                    return lookupNodes.Select(x => GetValue(x, culture, dateTimeFormat, numberFormat))
                        .ToString(", ");
                }

                var lookupNode = NodeManager.GetNode(GetValue<Guid>());

                return GetValue(lookupNode, culture, dateTimeFormat, numberFormat);
            }
            switch (NodeTypeProperty.DataType.SQLDataTypeName.ToLower())
            {
                case "datetime":
                    return GetValue<DateTime>().ToString(dateTimeFormat ?? "D", culture.DateTimeFormat);
                case "bit":
                    return Translator.Translate("Medianamik_Global", GetValue<String>());
                case "int":
                case "float":
                case "double":
                    return GetValue<Double>().ToString(numberFormat ?? "G", culture.NumberFormat);
                default:
                    return GetValue<String>();
            }
        }

        private string GetValue(Node lookupNode, CultureInfo culture, string dateTimeFormat, string numberFormat)
        {
            return lookupNode.Instances[culture]
                .Properties[NodeTypeProperty.PrimaryPropertyID.GetValueOrDefault()]
                .GetStringValue(culture, dateTimeFormat, numberFormat);
        }
        public string GetStringValue(CultureInfo culture)
        {
            return GetStringValue(culture, "D", "G");
        }

        public virtual void SetValue(object pValue)
        {
            if (pValue == null)
            { Value = null; }
            else
            {
                TypeConverter tc = TypeDescriptor.GetConverter(pValue.GetType());
                string value = tc.ConvertToInvariantString(pValue);
                ValidateValue(value);
                Value = value;
            }

        }

        internal virtual void ValidateValue(string pValue)
        {
            if (!IsRequired && pValue == null)
                throw new CoreException("Value of property " + Name + " cannot be null.");
            if (pValue != null)
            {
                //if (!Sugar.Types.ValidateDataType(DataTypeName, pValue))
                //    throw new Exception("Value of property " + Name + " does not respect specified DataType (" + DataTypeName + ").");

                if (NodeTypeProperty.MaxLength.HasValue && pValue.Length > NodeTypeProperty.MaxLength.Value)
                    throw new Exception("Maximum value length (" + NodeTypeProperty.MaxLength + ") exceeded for property : " + Name + ".");

                //TODO: Autre validations (Scale, Precision, etc.)
            }
        }

        public IMedianamikControl GetMedianamikControl(bool pEnableClientScriptValidation, 
            bool pAutoGenerateValidators, string tabName)
        {
            return GetMedianamikControl(pEnableClientScriptValidation, pAutoGenerateValidators, tabName,
                true, String.Empty, String.Empty, String.Empty, false, false, null);
        }
        public IMedianamikControl GetMedianamikControl(bool pEnableClientScriptValidation, bool pAutoGenerateValidators, string tabName, 
            Guid medianamikControlId)
        {
            return GetMedianamikControl(pEnableClientScriptValidation, pAutoGenerateValidators,
                tabName, true, String.Empty, String.Empty, String.Empty, false, false, medianamikControlId, null);
        }
        public virtual IMedianamikControl GetMedianamikControl(bool pEnableClientScriptValidation, 
            bool pAutoGenerateValidators, string tabName, bool pDisplayLabel,
            string pLabel, string pLabelLanguageExtensionText, string pLabelExtensionText, 
            bool hideMultiLingualControls, bool hideNeutralLanguageIndication, bool? isRequired)
        {
            return GetMedianamikControl(pEnableClientScriptValidation, pAutoGenerateValidators, tabName, pDisplayLabel,
             pLabel, pLabelLanguageExtensionText, pLabelExtensionText, hideMultiLingualControls,  hideNeutralLanguageIndication,
             MedianamikControl.CreateInstance(), isRequired);
        }

        public virtual IMedianamikControl GetMedianamikControl(bool pEnableClientScriptValidation, 
            bool pAutoGenerateValidators, string tabName, bool pDisplayLabel,
            string pLabel, string pLabelLanguageExtensionText, string pLabelExtensionText,
            bool hideMultiLingualControls, bool hideNeutralLanguageIndication, Guid medianamikControlId, bool? isRequired)
        {
            return GetMedianamikControl(pEnableClientScriptValidation, pAutoGenerateValidators, tabName, pDisplayLabel,
             pLabel,pLabelLanguageExtensionText, pLabelExtensionText, hideMultiLingualControls, hideNeutralLanguageIndication,
             ConfigManager.GetMedianamikControl(medianamikControlId).CreateInstance(), isRequired);
        }

        private IMedianamikControl GetMedianamikControl(bool pEnableClientScriptValidation, 
            bool pAutoGenerateValidators, string tabName, bool pDisplayLabel,
            string pLabel, string pLabelLanguageExtensionText, string pLabelExtensionText,
            bool hideMultiLingualControls, bool hideNeutralLanguageIndication, IMedianamikControl medianamikControl, bool? isRequired)
        {
            medianamikControl.SetLoadEventHandler((sender, args) =>
            {
                medianamikControl.DisplayLabel = pDisplayLabel;
                medianamikControl.AutoGenerateValidators = pAutoGenerateValidators;
                medianamikControl.EnableClientScriptValidation = pEnableClientScriptValidation;

                if (pDisplayLabel)
                {
                    medianamikControl.LabelLanguageExtensionText = pLabelLanguageExtensionText;
                    medianamikControl.Label = pLabel;
                    medianamikControl.LabelExtensionText = pLabelExtensionText;
                }

                medianamikControl.HaveTooltip = HaveTooltip;
                medianamikControl.DataSource = GetValue(medianamikControl.TypeOfValue);
                medianamikControl.ControlInfo = IsOwnProperty
                    ? NodeTypeProperty.MedianamikControlInfo
                    : NodeTypeProperty.PrimaryMedianamikControlInfo;
                medianamikControl.ValidationExpression = (ValidationRegex == null) ? String.Empty : ValidationRegex.ToString();
                medianamikControl.PropertyName = NodeTypeProperty.Name;

                medianamikControl.IsRequired = isRequired.HasValue ? 
                    isRequired.Value : NodeTypeProperty.IsRequired;

                medianamikControl.IsUnique = NodeTypeProperty.IsUnique;
                medianamikControl.NodeId = _nodeId;
                medianamikControl.NodeTypePropertyId = NodeTypeProperty.ID;
                medianamikControl.NodeTypeId = Node.NodeType.ID;
                medianamikControl.TypeName = Node.NodeType.Name;
                medianamikControl.ParentNodeId = Node.ParentID; // TODO: Attention!
                medianamikControl.EditionCultureName = Culture.Name;
                medianamikControl.HideMultiLingualControls = hideMultiLingualControls;
                medianamikControl.HideNeutralLanguageIndication = hideNeutralLanguageIndication;
                medianamikControl.MaxLength = NodeTypeProperty.MaxLength;
                medianamikControl.IsLanguageNeutral = NodeTypeProperty.IsLanguageNeutral;
                medianamikControl.TabName = tabName;
            });

            return medianamikControl;
        }

        /// <summary>
        /// Marque la propriété comme New
        /// Pour créer un nouvel enregistrement de la valeur dans la BD
        /// </summary>
        internal virtual void MarkNew()
        {
            IsNew = true;
            Revision = null;
            ID = default(int?);
        }

        internal virtual void OnBeforeSave()
        {
            if(Revision.HasValue && IsDirty )
            {
                //On ne doit jamais effacer une revision existante
                MarkNew();
            }
        }

        internal void MarkSaved()
        {

            IsNew = false;
            if(this.GetType() != typeof(ManyToManyNodeProperty))
            {
                InitialValue = Value;
            }
        }

        private readonly EventHandlerList _events = new EventHandlerList();
        private static readonly object _valueSetEvent = new object();
        internal event EventHandler ValueSet
        {
            add
            {
                _events.AddHandler(_valueSetEvent, value);
            }
            remove
            {
                _events.RemoveHandler(_valueSetEvent, value);
            }
        }
        protected void OnValueSet(EventArgs eventArgs)
        {
            var eh = _events[_valueSetEvent] as EventHandler ;
            if(eh!= null)
            {
                eh(this, eventArgs);
            }
        }
    }
}